// CoreCalc, a spreadsheet core implementation   

// ----------------------------------------------------------------------
// Copyright (c) 2006-2009 Peter Sestoft

// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use, copy,
// modify, merge, publish, distribute, sublicense, and/or sell copies
// of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:

//  * The above copyright notice and this permission notice shall be
//    included in all copies or substantial portions of the Software.

//  * The software is provided "as is", without warranty of any kind,
//    express or implied, including but not limited to the warranties of
//    merchantability, fitness for a particular purpose and
//    noninfringement.  In no event shall the authors or copyr  ight
//    holders be liable for any claim, damages or other liability,
//    whether in an action of contract, tort or otherwise, arising from,
//    out of or in connection with the software or the use or other
//    dealings in the software.
// ----------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.IO;                        // MemoryStream, Stream
using System.Text;
using System.Diagnostics;
using CoreCalc.SheetDefinedFunctions;

// The Cell class and its subclasses represent the possible contents 
// of a spreadsheet cell.

namespace CoreCalc {

  // A Cell is what populates one cell of a Sheet (if anything)

  public abstract class Cell : IDepend {
    
    public abstract Value Eval(Sheet sheet, int col, int row);

    public abstract Cell MoveContents(int deltaCol, int deltaRow);

    public abstract void InsertRowCols(Dictionary<Expr, Adjusted<Expr>> adjusted,
               Sheet modSheet, bool thisSheet,
               int R, int N, int r, bool doRows);

    public abstract void Reset();

    // Show computed value
    public String ShowValue(Sheet sheet, int col, int row) {
      Value v = Eval(sheet, col, row);
      return v != null ? v.ToString() : "";
    }

    // Show constant or formula or array formula
    public abstract String Show(int col, int row, Formats fo);

    // Parse string to cell contents at (col, row) in given workbook 
    public static Cell Parse(String text, Workbook workbook, int col, int row) {
      Scanner scanner = new Scanner(IOFormat.MakeStream(text));
      Parser parser = new Parser(scanner);
      Cell cell = parser.ParseCell(workbook, col, row);
      return cell;
    }

    public abstract void DependsOn(FullCellAddr here, Act<FullCellAddr> dependsOn);
  }

  // ----------------------------------------------------------------
  // A ConstCell is a cell that contains a constant only.  
  // In contrast to general cells, it is immutable and can be shared

  abstract class ConstCell : Cell {
    public override Cell MoveContents(int deltaCol, int deltaRow) {
      return this;
    }

    public override void InsertRowCols(Dictionary<Expr, Adjusted<Expr>> adjusted,
               Sheet modSheet, bool thisSheet,
               int R, int N, int r, bool doRows) { }

    public override void Reset() { }

    public override void DependsOn(FullCellAddr here, Act<FullCellAddr> dependsOn)
    { }
  }

  // ----------------------------------------------------------------
  // A NumberCell is a cell containing a floating-point constant;
  // not a constant number-valued expression

  sealed class NumberCell : ConstCell {
    public readonly NumberValue value;        // Non-null

    public NumberCell(double d) {
      Debug.Assert(!Double.IsNaN(d) && !Double.IsInfinity(d));
      value = (NumberValue)NumberValue.Make(d);
    }

    public override Value Eval(Sheet sheet, int col, int row)
    {
        if (SheetDefinedFunctions.Accellerator.UnknownAccNode.inputCells != null)
        {
            var index = Array.FindIndex<FullCellAddr>(SheetDefinedFunctions.Accellerator.UnknownAccNode.inputCells, (Predicate<FullCellAddr>)delegate(FullCellAddr x) { return x.Equals(new FullCellAddr(col, row, sheet)); });

            if (index != -1)
            {
                return new NumberValue((double)SheetDefinedFunctions.Accellerator.UnknownAccNode.values[index]);
            }
        }
        return value;
    }

    public override String Show(int col, int row, Formats fo) {
      return value.value.ToString();
    }
  }

  // ----------------------------------------------------------------
  // A QuoteCell is a cell containing a single-quoted string constant; 
  // not a constant string-valued expression

  sealed class QuoteCell : ConstCell {
    public readonly TextValue value;          // Non-null

    public QuoteCell(String s) {
      this.value = new TextValue(s);
    }

    public override Value Eval(Sheet sheet, int col, int row) {
      return value;
    }

    public override String Show(int col, int row, Formats fo) {
      return "'" + value.value;
    }
  }

  // ----------------------------------------------------------------
  // A TextCell is a cell containing a double-quoted string constant; 
  // not a constant string-valued expression

  sealed class TextCell : ConstCell
  {
    public readonly TextValue value;          // Non-null

    public TextCell(String s)
    {
      this.value = new TextValue(s);
    }

    public override Value Eval(Sheet sheet, int col, int row)
    {
      return value;
    }    

      public override String Show(int col, int row, Formats fo)
    {
      return "\"" + value.value + "\"";
    }
  }

  // ----------------------------------------------------------------
  // A Formula is a non-null caching expression contained in one cell.

  sealed class Formula : Cell {
    public readonly Workbook workbook;  // Non-null
    private Expr e;                     // Non-null
    private bool visited, uptodate;
    private Value v;                    // Up to date if uptodate==workbook.Set

    public Formula(Workbook workbook, Expr e)
    {
      // Assert workbook != null and e != null
      this.workbook = workbook;
      this.e = e;
      this.visited = this.uptodate = workbook.Set;
    }

    public Formula(Formula f) : this(f.workbook, f.e) { }

    public static Formula Make(Workbook workbook, Expr e)
    {
      if (e == null)
        return null;
      else
        return new Formula(workbook, e);
    }

    // FIXME: Adequate for moving one cell, but block moves and row/column
    // inserts should avoid the duplication and unsharing of expressions. 
    public override Cell MoveContents(int deltaCol, int deltaRow) {
      return new Formula(workbook, e.Move(deltaCol, deltaRow));
    }

    // Evaluate expression if necessary, and cache its value
    public Value RealEval(Sheet sheet, int col, int row)
    {
      if (uptodate != workbook.Set)
      {
        if (visited == workbook.Set)
          throw new CyclicException("Cyclic cell reference: "
                                    + Show(col, row, workbook.Format));
        else
        {
          visited = workbook.Set;
          v = e.Eval(sheet, col, row);
          uptodate = workbook.Set;
        }
      }
      return v;
    }

    public override Value Eval(Sheet sheet, int col, int row)
    {
        if (SheetDefinedFunctions.Accellerator.UnknownAccNode.inputCells != null)
        {
            var index = Array.FindIndex<FullCellAddr>(SheetDefinedFunctions.Accellerator.UnknownAccNode.inputCells, (Predicate<FullCellAddr>)delegate(FullCellAddr x) { return x.Equals(new FullCellAddr(col, row, sheet)); });

            if (index != -1)
            {
                return new NumberValue((double)SheetDefinedFunctions.Accellerator.UnknownAccNode.values[index]);
            }
        }
        return RealEval(sheet, col, row);
    }




    public override void InsertRowCols(Dictionary<Expr, Adjusted<Expr>> adjusted,
               Sheet modSheet, bool thisSheet,
               int R, int N, int r, bool doRows) {
      Adjusted<Expr> ae;
      if (adjusted.ContainsKey(e) && r < adjusted[e].upper)
        // There is a valid cached adjusted expression
        ae = adjusted[e];
      else {
        // Compute a new adjusted expression and insert into the cache
        ae = e.InsertRowCols(modSheet, thisSheet, R, N, r, doRows);
        Console.WriteLine("Making new adjusted at rowcol " + r
                          + "; upper = " + ae.upper);
        if (ae.same) { // For better sharing, reuse unadjusted e if same
          ae = new Adjusted<Expr>(e, ae.upper, ae.same);
          Console.WriteLine("Reusing expression");
        }
        adjusted[e] = ae;
      }
      System.Diagnostics.Debug.Assert(r < ae.upper, "Formula.InsertRowCols");
      e = ae.e;
    }

    // Reset recomputation flags after a circularity has been found
    public override void Reset() {
      visited = uptodate = workbook.Set;
    }

    public override void DependsOn(FullCellAddr here, Act<FullCellAddr> dependsOn)
    {
      e.DependsOn(here, dependsOn);
    }

    public override String Show(int col, int row, Formats fo) {
      return "=" + e.Show(col, row, 0, fo);
    }

    public Expr Expr {
      get { return e; }
      set { e = value; }
    }
  }

  // ----------------------------------------------------------------
  // An ArrayFormula is a cached array formula shared among several
  // cells, each cell accessing one part of the result array.  Several
  // ArrayFormula cells share one CachedArrayFormula cell; evaluation
  // of one cell will evaluate the formula and cache its (array) value
  // for the other cells to use.

  sealed class ArrayFormula : Cell {
    public readonly CachedArrayFormula cmf;  // Non-null
    private readonly CellAddr ca;  // Cell's location within array value

    public ArrayFormula(CachedArrayFormula cmf, CellAddr ca) {
      this.cmf = cmf; this.ca = ca;
    }

    public ArrayFormula(CachedArrayFormula cmf, int col, int row)
      : this(cmf, new CellAddr(col, row)) { }

    public override Value Eval(Sheet sheet, int col, int row) {
      ArrayValue array = cmf.Eval(sheet, col, row);
      if (array != null)
        return array[ca];
      else
        return ErrorValue.Make("Not array");
    }

    public bool Contains(int col, int row) {
      return cmf.ulCa.col <= col && col <= cmf.lrCa.col
          && cmf.ulCa.row <= row && row <= cmf.lrCa.row;
    }

    public override Cell MoveContents(int deltaCol, int deltaRow) {
      // FIXME: loses sharing of the CachedArrayFormula; but then again
      // a array formula should never be moved cell by cell, but in its
      // entirety, and in one go.
      return new ArrayFormula(cmf.MoveContents(deltaCol, deltaRow), ca);
    }

    public override void InsertRowCols(Dictionary<Expr, Adjusted<Expr>> adjusted,
               Sheet modSheet, bool thisSheet,
               int R, int N, int r, bool doRows) {
      // FIXME: Update underlying formula only once!
    }

    public override void Reset() {
      cmf.Reset();
    }

    public override void DependsOn(FullCellAddr here, Act<FullCellAddr> dependsOn)
    {
      // It seems that this could uselessly be called on every cell 
      // that shares the formula, but this will not happen on function sheets
      cmf.formula.DependsOn(here, dependsOn);
    }

    public override String Show(int col, int row, Formats fo) {
      return "{" + cmf.Show(col, row, fo) + "}";
    }
  }

  // ----------------------------------------------------------------
  // A cached array formula is shared between multiple array cells.
  // It contains a (caching) array-valued formula, the original cell
  // address of that formula, and the upper left and lower right corners
  // of the array.

  sealed class CachedArrayFormula {
    public readonly Formula formula;             // Non-null
    private readonly int formulaCol, formulaRow; // Location of formula entry
    public readonly CellAddr ulCa, lrCa;         // Corners of array formula
    // Invariant: Every cell within sheet[ulCa, lrCa] is a ArrayFormula
    // whose CachedArrayFormula instance is this one.

    public CachedArrayFormula(Formula formula, int formulaCol, int formulaRow,
                              CellAddr ulCa, CellAddr lrCa) {
      if (formula == null)
        throw new Exception("CachedArrayFormula arguments");
      else {
        this.formula = formula;
        this.formulaCol = formulaCol;
        this.formulaRow = formulaRow;
        this.ulCa = ulCa;
        this.lrCa = lrCa;
      }
    }

    // Evaluate expression if necessary, cast, and return
    public ArrayValue Eval(Sheet sheet, int col, int row) {
      return formula.Eval(sheet, formulaCol, formulaRow) as ArrayValue;
    }

    public CachedArrayFormula MoveContents(int deltaCol, int deltaRow) {
      // FIXME: Unshares the formula, shouldn't ...
      return new CachedArrayFormula((Formula)formula.MoveContents(deltaCol, deltaRow),
                                     formulaCol, formulaRow, ulCa, lrCa);
    }

    // Reset recomputation flags after a circularity has been found
    public void Reset() {
      formula.Reset();
    }

    public String Show(int col, int row, Formats fo) {
      return formula.Show(col, row, fo);
    }
  }
}
